12장 딱 하나뿐인 딸린 자원 (싱글톤 서브리소스) — 입문판

출처: API Design Patterns (Manning) · JJ Geewax | 참고: https://google.aip.dev/156

이 장에서 딱 4가지만 기억하면 됩니다. 1. 하나의 자원 안에 성격이 너무 다른 데이터가 섞여 있으면 떼어내자 (왜 떼어내는가) 2. 떼어낸 그 조각이 '속성도 아니고 완전한 자원도 아닌 중간 형태'라는 것 (그게 뭔가) 3. 그 중간 형태는 조회·수정만 따로 하고, 생성·삭제는 부모를 따라간다는 것 (어떻게 동작하나) 4. 이 조각은 반드시 부모 밑에 있어야 한다는 것 (어디에 두나)

학습 목표

이 장을 마치면 다음을 할 수 있습니다.

  • 자원의 일부 데이터를 따로 떼어내야 하는 세 가지 상황을 설명한다.
  • 싱글톤 서브리소스가 단순 속성, 완전한 자원과 어떻게 다른지 구분한다.
  • 싱글톤 서브리소스에서 어떤 표준 메서드가 동작하고 어떤 것이 동작하지 않는지 설명한다.
  • 삭제 대신 reset을 쓰는 이유를 설명한다.
  • 싱글톤 서브리소스를 자원 계층 어디에 두어야 하는지 설명한다.

12.1 한 자원에 다 넣었더니 터지는 순간

먼저, 이런 적 있죠?

승차 공유 앱을 만든다고 해봅시다. 운전기사 한 명을 표현하려고 자원 하나를 이렇게 짰어요.

Driver {
  id: "drivers/1"
  name: "홍길동"
  licensePlate: "12가3456"
  lat: 37.5665      ← 차가 움직이는 동안 1초마다 바뀜
  long: 126.9780    ← 차가 움직이는 동안 1초마다 바뀜
}

언뜻 깔끔해 보이죠. 그런데 운영하다 보면 자꾸 불편한 일이 생깁니다.

  • 기사 이름만 보고 싶은데, 호출할 때마다 위치 좌표까지 딸려옵니다.
  • 위치 한 칸만 바꾸고 싶은데, Driver 전체를 수정하는 요청을 보내야 합니다.
  • 이름은 거의 안 바뀌는데 위치는 1초마다 바뀝니다. 둘이 한 자원에 묶여 있으니 위치 갱신이 들어올 때마다 같은 자원을 건드리게 됩니다.

이게 바로 "한 자원에 성격 다른 데이터를 다 욱여넣어서 터지는" 장면입니다.

그게 바로 '분리'가 필요한 순간이다

해결책은 단순합니다. 성격이 너무 다른 데이터는 자원에서 떼어내 따로 두자는 것.

새 용어: 서브리소스(sub-resource) — 어떤 자원에 딸려 있는 자식 자원입니다. 일상 비유로 보면, 사람(부모)에게 딸린 '지갑'(자식)처럼 본체에 붙어 다니지만 따로 열어볼 수 있는 것입니다.

좀 전 예시를 이렇게 고칠 수 있습니다.

Driver {
  id: "drivers/1"
  name: "홍길동"
  licensePlate: "12가3456"
}                          ← 위치는 여기서 빠졌습니다

DriverLocation {
  id: "drivers/1/location"  ← 부모(drivers/1)에 딸린 자식
  lat: 37.5665
  long: 126.9780
}

이제 이름을 볼 때는 위치가 안 따라오고, 위치를 바꿀 때는 Driver 전체를 건드릴 필요가 없습니다.

떼어내는 이유 딱 3가지

데이터를 떼어낼지 말지는 다음 세 가지 신호로 판단합니다.

이유 1 — 크기 (너무 무겁다)

새 용어: 메타데이터(metadata) — '데이터에 대한 설명 데이터'입니다. 일상 비유로 보면, 택배 상자 안의 물건이 본 데이터라면 상자에 붙은 송장(보낸 사람, 무게, 받는 곳)이 메타데이터입니다.

사진 같은 큰 파일을 자원에 통째로 넣어두면, 이름만 확인하려 해도 큰 파일까지 같이 내려받게 됩니다.

# 떼어내기 전 — 메타데이터만 보고 싶은데 수 GB가 딸려옴
GetObject() → 송장 정보 + 실제 파일(수 GB)   ← 낭비

# 떼어낸 후 — 필요한 것만
GetObjectMetadata() → 송장 정보만 (수 KB)
GetObjectData()     → 실제 파일만 (정말 필요할 때만)

이유 2 — 보안 (보는 사람이 다르다)

직원 정보 안에 급여가 같이 들어 있으면, 직원 정보를 가져오는 모든 사람이 급여까지 보게 됩니다.

# 떼어내기 전 — 위험
GetEmployee() → 이름, 부서, 전화번호, 급여(!)   ← 누구나 봄

# 떼어낸 후 — 급여만 따로
GetEmployee()       → 이름, 부서, 전화번호 (일반 직원도 조회)
GetEmployeeSalary() → 급여 (인사팀만 조회)

이유 3 — 휘발성 (바뀌는 속도가 다르다)

새 용어: 휘발성(volatility) — 데이터가 얼마나 자주 바뀌는지를 뜻합니다. 일상 비유로 보면, 사람의 '이름'은 평생 거의 안 바뀌지만 '지금 위치'는 걸을 때마다 바뀌는 것과 같습니다. 자주 바뀌는 쪽이 휘발성이 높습니다.

새 용어: 쓰기 경합(write contention) — 여러 요청이 같은 자원을 동시에 고치려고 몰릴 때 생기는 충돌입니다. 일상 비유로 보면, 한 개의 화이트보드에 여러 명이 동시에 쓰려고 펜을 들이대 서로 밀치는 상황입니다.

이름은 거의 안 바뀌는데 위치는 매초 바뀝니다. 둘을 한 자원에 묶어두면 위치 갱신이 쏟아질 때마다 같은 자원으로 쓰기가 몰려 경합이 생깁니다.

# 떼어낸 후 — 서로 방해 안 함
UpdateDriver()         → 이름, 차량번호 (가끔)
UpdateDriverLocation() → 위치 (매초)

비유로 한눈에 — 표로 묶기

일상 비유 API/코드 상황 위험·주의
송장만 보려는데 택배 물건까지 받음 GetObject가 수 GB 파일까지 반환 작은 정보 보려고 큰 전송 비용 발생 → 크기로 분리
직원 명단에 급여가 같이 적힘 GetEmployee가 급여까지 반환 권한 없는 사람이 민감 정보 열람 → 보안으로 분리
한 화이트보드에 여러 명이 동시에 씀 위치 갱신이 Driver 자원에 몰림 쓰기 경합으로 충돌·지연 → 휘발성으로 분리

여기까지 체크포인트

  • 크기가 너무 큰 조각은 떼어낸다.
  • 보는 사람(권한)이 다른 조각은 떼어낸다.
  • 바뀌는 속도가 크게 다른 조각은 떼어낸다.
  • 핵심은 "같은 자원 안인데 성격이 다른 데이터"를 가려내는 것이다.

12.2 떼어낸 그 조각의 정체 — 속성과 자원의 중간

먼저, 이런 적 있죠?

떼어내긴 했는데, "이걸 뭐라고 불러야 하지?" 하고 헷갈린 적 있을 겁니다. 완전히 독립된 자원이라기엔 부모 없이는 존재할 수 없고, 그냥 속성이라기엔 따로 조회·수정이 됩니다.

그게 바로 '싱글톤 서브리소스'다

새 용어: 싱글톤 서브리소스(singleton sub-resource) — 부모 자원에 딱 하나만 딸려 있는 특별한 자식 자원입니다. 속성처럼 부모와 함께 생기고 사라지지만, 자원처럼 따로 조회·수정할 수 있는 중간 형태입니다. 일상 비유로 보면, 스마트폰의 '배터리'처럼 폰에 딱 하나 들어 있고 폰과 함께 생기지만 충전량은 따로 들여다볼 수 있는 것입니다.

'싱글톤'은 '딱 하나뿐'이라는 뜻입니다. 그래서 이 장 제목이 "딱 하나뿐인 딸린 자원"입니다.

스마트폰으로 셋을 구분해 보자

스마트폰 비유 정체 특징
색상 "블랙" 단순 속성 폰의 일부라 따로 관리 안 함
배터리 (충전량 85%) 싱글톤 서브리소스 폰에 딱 하나, 폰과 함께 생기지만 상태는 따로 확인
설치된 앱들 (카톡, 유튜브…) 일반 서브리소스(여러 개) 각각 따로 설치·삭제, 개수 제한 없음

코드로 보면 이렇게 생겼습니다.

스마트폰 (부모 자원)
├── 색상: "블랙"          ← 단순 속성
├── 배터리                ← 싱글톤 서브리소스 (하나뿐, 따로 조회 가능)
│   ├── 충전량: 85%
│   └── 건강도: 92%
└── 앱[]                  ← 일반 서브리소스 컬렉션 (여러 개)

속성 / 싱글톤 서브리소스 / 일반 자원 비교

항목 단순 속성 싱글톤 서브리소스 일반 자원
개수 해당 없음 부모당 1개 여러 개 가능
생성 부모와 함께 부모와 함께 자동 직접 만들어야 함
삭제 부모와 함께 부모와 함께 자동 직접 지워야 함
조회 부모 조회에 포함 따로 조회 가능 따로 조회 가능
수정 부모 수정에 포함 따로 수정 가능 따로 수정 가능
고유 주소(URL) 없음 있음 (부모 주소 기반) 있음

여기까지 체크포인트

  • 싱글톤 서브리소스 = 속성과 자원의 하이브리드(중간 형태)다.
  • 부모당 딱 하나만 존재한다.
  • 생성·삭제는 속성처럼 자동, 조회·수정은 자원처럼 따로다.

12.3 어떻게 동작하나 — 표준 메서드

먼저, 이런 적 있죠?

새 자원을 만들면 으레 다섯 가지를 다 만들죠. 조회(Get), 생성(Create), 수정(Update), 삭제(Delete), 목록(List). 그런데 싱글톤 서브리소스에 다섯 개를 다 붙이면 이상한 게 생깁니다. "부모당 하나뿐인데 목록(List)이 왜 필요하지?" "부모를 만들면 자동으로 생기는데 생성(Create)을 따로 부르면?"

그게 바로 '일부만 동작한다'는 뜻이다

싱글톤 서브리소스는 다섯 메서드 중 조회와 수정만 평범하게 동작합니다. 나머지는 부모를 따라가거나 아예 없습니다.

표준 메서드 어떻게 동작? 설명
Get(조회) 자원처럼 따로 고유 주소로 직접 조회
Update(수정) 자원처럼 따로 일부 칸만 골라 수정 가능
Create(생성) 속성처럼 자동 따로 못 만듦, 부모 생길 때 자동 생성
Delete(삭제) 속성처럼 자동 따로 못 지움, 부모 지울 때 자동 삭제
List(목록) 없음 부모당 1개뿐이라 목록이 의미 없음

Get과 Update — 자원처럼 따로

조회와 수정은 부모 주소가 아니라 자기 고유 주소를 씁니다.

# 조회 — 위치만 직접 가져옴
GET /drivers/1/location
→ { lat: 37.5665, long: 126.9780 }

# 수정 — 위도 한 칸만 바꿈
PATCH /drivers/1/location
Body: { lat: 37.5700 }
→ { lat: 37.5700, long: 126.9780 }

새 용어: PATCH — 자원의 일부 칸만 골라 고치는 HTTP 방식입니다. 일상 비유로 보면, 서류 전체를 새로 쓰는 게 아니라 틀린 한 줄만 수정 테이프로 고치는 것입니다. (전체를 통째로 바꾸는 PUT과 대비됩니다.)

핵심은 주소가 drivers/1(부모)이 아니라 drivers/1/location(자기 자신)이라는 점입니다.

잠깐 — 어느 칸을 고칠지 서버가 어떻게 알까? (fieldMask)

방금 PATCH로 위도 한 칸만 바꿨죠. 그런데 생각해 보면 이상합니다. 서버 입장에서는 "위도만 바꾸고 경도는 그대로 두라"는 건지, "경도는 비우라(null로 만들라)"는 건지 헷갈릴 수 있습니다.

# 헷갈리는 상황 — 위도만 보냈는데...
PATCH /drivers/1/location
Body: { lat: 37.5700 }
→ 서버: "경도(long)는 안 보냈네? 비우라는 건가, 그대로 두라는 건가?"

이 헷갈림을 없애는 표준 방식이 바로 fieldMask입니다.

새 용어: 필드 마스크(fieldMask) — PATCH로 일부만 고칠 때 "이 칸들만 건드려라"고 서버에 콕 집어 알려주는 목록입니다. 8장(부분 업데이트)에서 다룬 그 표준 방식이며, 싱글톤 서브리소스 수정에도 똑같이 씁니다. 일상 비유로 보면, 서류를 맡기면서 "3번 줄하고 7번 줄만 고쳐 주세요"라고 포스트잇으로 콕 집어 붙여 주는 것입니다. 안 적은 줄은 손도 안 댑니다.

fieldMask에 적힌 칸만 고치고, 안 적힌 칸은 그대로 둡니다.

# fieldMask로 "lat만 고쳐라"고 콕 집음
PATCH /drivers/1/location
Body: {
  driverLocation: { lat: 37.5700 },
  fieldMask: ["lat"]      ← lat 한 칸만 건드려라
}
→ { lat: 37.5700, long: 126.9780 }   ← long은 손도 안 댐, 그대로

비유 → API → 위험을 표로 묶으면 이렇습니다.

일상 비유 API/코드 동작 위험·주의
포스트잇으로 "3번 줄만 고쳐 주세요" fieldMask: ["lat"] 로 lat만 지정 적은 칸만 바뀜, 안 적은 칸은 보존
포스트잇 없이 서류를 통째로 맡김 fieldMask 없이 PATCH 서버가 빠진 칸을 비울지 둘지 헷갈림 → 데이터 유실 위험

잘못된 예와 올바른 예를 나란히 보면 차이가 분명합니다.

# 잘못된 예 — fieldMask 없이 일부만 보냄
PATCH /drivers/1/location
Body: { lat: 37.5700 }
→ 위험: 서버가 long을 "비우라"로 해석하면 long이 null로 날아감

# 올바른 예 — fieldMask로 고칠 칸을 명시
PATCH /drivers/1/location
Body: { driverLocation: { lat: 37.5700 }, fieldMask: ["lat"] }
→ lat만 안전하게 바뀌고 long은 그대로 보존

요약하면, 부분 수정(PATCH)에는 "어느 칸을 고칠지" 알려주는 fieldMask를 함께 보내는 것이 표준입니다.

Create — 부모 만들면 자동으로 따라옴

잘못된 기대 vs 올바른 동작을 비교해 봅시다.

# 잘못된 기대 — 이런 메서드는 없습니다
POST /drivers/1/location   ← CreateDriverLocation? 존재하지 않음

# 올바른 동작 — 부모를 만들면 자동 생성
POST /drivers { name: "홍길동", licensePlate: "12가3456" }
→ Driver { id: "drivers/1", ... }

# 만들자마자 바로 조회됨 (이미 존재함, 기본값 상태)
GET /drivers/1/location
→ { lat: null, long: null }   ← 기본값으로 초기화돼 있음

Delete — 부모 지우면 같이 사라짐

새 용어: 캐스케이드 삭제(cascade delete) — 부모를 지우면 딸린 자식까지 줄줄이 함께 지워지는 동작입니다. 일상 비유로 보면, 건물을 철거하면 그 안의 방들도 같이 사라지는 것과 같습니다.

# 위치가 있다
GET /drivers/12345/location
→ { lat: ..., long: ... }

# 부모 기사를 지운다
DELETE /drivers/12345
→ 200 OK

# 위치도 같이 사라졌다
GET /drivers/12345/location
→ 404 Not Found

핵심: 싱글톤 서브리소스는 따로 지우는 메서드가 없습니다. 부모가 사라지면 캐스케이드 삭제로 함께 사라집니다.

비유로 한눈에 — 표로 묶기

일상 비유 API/코드 동작 위험·주의
배터리 충전량만 따로 확인 GET /drivers/1/location 부모 주소가 아닌 자기 주소를 써야 함
폰을 사면 배터리도 같이 들어 있음 Driver 생성 시 Location 자동 생성 CreateDriverLocation을 따로 만들면 안 됨
폰을 버리면 배터리도 같이 버려짐 Driver 삭제 시 Location 캐스케이드 삭제 따로 삭제 메서드를 두면 일관성이 깨짐

12.3.2 지우는 대신 '초기화' — reset

먼저, 이런 적 있죠?

"위치 정보를 비우고 싶다"는 요구가 들어옵니다. 습관적으로 DELETE를 떠올리죠. 그런데 싱글톤 서브리소스는 부모가 살아 있는 한 지울 수가 없습니다. 지우는 메서드 자체가 없으니까요.

그게 바로 'reset'이 필요한 이유다

새 용어: 사용자 메서드(custom method) — 표준 5종(조회·생성·수정·삭제·목록)으로 표현하기 어려운 특별한 동작에 이름을 붙여 만든 메서드입니다. 일상 비유로 보면, 일반 버튼들(전원·볼륨) 말고 따로 만든 '초기화' 버튼 같은 것입니다.

새 용어: reset 메서드 — 자원을 공장 출고 상태(기본값)로 되돌리는 사용자 메서드입니다. 일상 비유로 보면, TV를 버리는 게 아니라 설정만 '공장 초기화'하는 버튼입니다.

지우는 게 아니라 기본값으로 되돌리는 것이라서 이름도 delete가 아니라 reset입니다.

# 지금 위치가 있다
GET /drivers/1/location
→ { lat: 37.5665, long: 126.9780 }

# 초기화한다 (지우는 게 아니라 비움)
POST /drivers/1/location:reset
→ { lat: null, long: null }

비유로 보면, TV 자체를 버리는 게 delete, 설정만 공장 초기화하는 게 reset입니다. 싱글톤 서브리소스에는 delete가 없으니 초기화는 reset으로 합니다.

비유로 한눈에 — 표로 묶기

일상 비유 API/코드 동작 위험·주의
TV 자체를 버림 DELETE 싱글톤 서브리소스엔 이 동작이 없음
TV 설정만 공장 초기화 POST /drivers/1/location:reset 비우려면 삭제가 아니라 reset을 써야 함

12.3.3 어디에 두나 — 계층 구조 규칙

표준 규칙 (기본 안전판)

싱글톤 서브리소스를 둘 위치는 규칙이 단순합니다.

규칙 1 — 반드시 부모 밑에 둔다.

✅ /drivers/1/location     ← 부모(Driver) 밑에 있음 (정답)
❌ /location               ← 최상위에 혼자 있음 (금지)

새 용어: 전역 공유 잠금(global shared lock) — 모든 사용자가 같은 하나의 자원을 함께 쓰려고 줄 서는 병목 상황입니다. 일상 비유로 보면, 온 동네가 화장실 한 칸을 같이 쓰는 것과 같습니다.

최상위에 혼자 두면 모든 클라이언트가 그 하나를 공유하게 됩니다. 그러면 모든 쓰기가 한 곳으로 몰려 전역 공유 잠금이라는 병목이 생깁니다.

규칙 2 — 싱글톤 서브리소스는 또 다른 자식의 부모가 되면 안 된다.

✅ /drivers/1/location          ← Driver → Location (정답)
❌ /drivers/1/location/signal   ← Location이 또 부모가 됨 (금지)

싱글톤이 누군가의 부모 노릇을 하기 시작하면, 그 순간 완전한 부모 자원처럼 무거운 책임을 다 떠안아야 합니다. 중간 형태라는 단순함이 깨지는 것이죠.

비유로 한눈에 — 표로 묶기

일상 비유 API/코드 상황 위험·주의
배터리는 폰 안에만 있음 /drivers/1/location 최상위에 혼자 두면 안 됨
온 동네가 화장실 한 칸 공유 /location (최상위) 전역 공유 잠금 병목 발생
배터리 밑에 또 부품을 매다는 격 /drivers/1/location/signal 싱글톤이 부모 노릇을 하면 단순함이 깨짐

전체를 합친 최종 모습

마지막으로 지금까지 배운 걸 한 API 정의로 묶어보겠습니다.

Ride Sharing API (v1)

// Driver — 다섯 표준 메서드 다 있음
GET    /drivers              (목록)
POST   /drivers              (생성)
GET    /drivers/1            (조회)
PATCH  /drivers/1            (수정)
DELETE /drivers/1            (삭제)

// DriverLocation — 조회·수정만 있음
GET    /drivers/1/location   (조회)
PATCH  /drivers/1/location   (수정)
// Create/Delete/List 없음 → 부모를 따라가거나 의미 없음

자원 정의는 이렇게 갈라집니다.

Driver {            ← 부모: 위치가 여기 없음
  id, name, licensePlate
}

DriverLocation {    ← 싱글톤 서브리소스: 부모 주소에 딸림
  id: "drivers/1/location"
  lat, long, updateTime
}

잠깐 — 매초 바뀌는 위치에 updateTime이 왜 붙지?

위 정의를 보면 위치 칸 옆에 updateTime이 슬쩍 끼어 있습니다. "위치는 1초마다 바뀌는데, 굳이 '마지막으로 바뀐 시각'까지 적어둘 필요가 있나?" 싶죠.

새 용어: updateTime — 이 자원이 마지막으로 갱신된 시각을 담는 칸입니다. 일상 비유로 보면, 버스 도착 안내 전광판에 뜨는 "방금 전 갱신"이라는 시각 표시와 같습니다. 숫자(도착 시간)만 보면 그게 1초 전 정보인지 5분 전 멈춘 정보인지 알 수 없으니, 갱신 시각을 같이 보여 주는 것입니다.

휘발성이 높은(자주 바뀌는) 데이터일수록 오히려 updateTime이 더 중요합니다. 받은 좌표가 방금 것인지, 신호가 끊겨 10초 전에 멈춘 것인지 구분해야 하기 때문입니다.

# 받은 응답
GET /drivers/1/location
→ { lat: 37.5665, long: 126.9780, updateTime: "2026-06-04T10:00:03Z" }
   ↑ 좌표만 보면 최신인지 알 수 없지만,
     updateTime을 보면 "3초 전 갱신"이라 살아있는 위치임을 안다

요약하면, 자주 바뀌는 데이터일수록 그 값이 '언제 것인지'가 중요하므로 updateTime을 함께 둡니다.

여기까지 체크포인트

  • Get/Update만 자원처럼 따로 동작한다.
  • Create/Delete는 부모를 따라 자동 처리된다.
  • 삭제 대신 reset으로 초기화한다.
  • 반드시 부모 밑에 두고, 최상위는 금지한다.
  • 싱글톤 서브리소스는 또 다른 자식의 부모가 될 수 없다.

12.4 떼어낼 때 생기는 두 가지 대가 (트레이드오프)

분리는 좋은 점만 있는 게 아닙니다. 대가가 둘 있습니다.

12.4.1 동시에 못 바꾼다 (원자성 없음)

새 용어: 원자성(atomicity) — 여러 작업이 한 묶음으로 '전부 성공 아니면 전부 실패'가 되는 성질입니다. 일상 비유로 보면, 계좌 이체에서 '내 통장 출금'과 '상대 통장 입금'이 둘 다 되거나 둘 다 안 되는 것과 같습니다.

부모와 싱글톤 서브리소스는 한 번의 요청으로 동시에 못 바꿉니다.

# 이런 한 방 요청은 불가능
"이름 바꾸면서 동시에 위치도 바꿔줘"

# 요청을 둘로 나눠야 함
PATCH /drivers/1           { name: "새이름" }
PATCH /drivers/1/location  { lat: 37.5665 }

그런데 이건 사실 의도된 결과입니다. 애초에 떼어낸 이유가 "따로 관리하려고"였으니까요. 만약 둘을 꼭 동시에 바꿔야 한다면, 그건 애초에 떼어내면 안 되는 데이터였다는 신호입니다.

12.4.2 같은 종류는 부모당 하나만

새 용어: 서브컬렉션(sub-collection) — 부모에 딸린 자식들을 여러 개 담는 묶음입니다. 일상 비유로 보면, 한 사람(부모)에게 신용카드가 여러 장(자식 묶음) 있는 것과 같습니다.

싱글톤은 '딱 하나'가 핵심입니다. 같은 종류를 부모당 두 개 이상 두고 싶다면, 그건 싱글톤이 아니라 일반 서브컬렉션입니다.

✅ /drivers/1/location     ← 위치 1개 (싱글톤, 정답)
❌ /drivers/1/locations    ← 위치 여러 개 (이건 일반 서브컬렉션)

여러 개가 필요하면 싱글톤을 억지로 쓰지 말고 일반 서브컬렉션으로 설계해야 합니다.

비유로 한눈에 — 표로 묶기

일상 비유 API/코드 상황 위험·주의
출금·입금이 한 묶음으로만 됨 부모+서브리소스 동시 수정 한 요청으로 못 함 → 요청 둘로 나눠야 함
카드가 여러 장 필요함 /drivers/1/locations 여러 개면 싱글톤이 아니라 서브컬렉션

12.5 직접 풀어보기 — 빈칸에서 독립까지

(1) 함께 풀기 (worked example)

문제: 운전기사의 위치를 조회하는 요청은? 풀이: 위치는 싱글톤 서브리소스이고, 조회는 자기 고유 주소로 합니다. → GET /drivers/1/location

(2) 빈칸 채우기 (부분 완성)

문제: 운전기사의 위치를 기본값으로 되돌리려면? 싱글톤 서브리소스에는 delete가 없으니, ___ 사용자 메서드를 씁니다.

POST /drivers/1/location:____

(채울 말: reset)

(3) 혼자 풀기 (독립 적용)

문제: 직원 자원에서 급여만 인사팀에게 보이게 하고 싶습니다. 어떻게 설계할까요?

정답을 보기 전에 직접 생각해 보세요. 힌트: 12.1에서 배운 '떼어내는 이유 3가지' 중 무엇에 해당하나요?

A: 보는 사람(권한)이 다른 데이터이므로 '보안' 이유로 분리합니다. 급여를 싱글톤 서브리소스로 떼어내, GetEmployee는 누구나, GetEmployeeSalary는 인사팀만 조회하도록 권한을 다르게 둡니다.

책 속 연습문제 Q&A

Q1. 속성이 어느 정도 크면 떼어내는 게 좋을까?

딱 떨어지는 숫자 기준은 없습니다. 부모 자원 크기의 대부분을 차지하거나, 대부분의 호출에서 필요 없는 데이터라면 분리를 고려합니다. 예를 들어 ACL이 규칙 수천 개를 가지면 분리 대상입니다. (새 용어: ACL(접근 제어 목록) — 누가 어떤 자원에 접근할 수 있는지 적어둔 명단입니다. 일상 비유로 보면, 행사장 입구의 '입장 가능자 명단'과 같습니다.)

Q2. 왜 조회와 수정만 지원하나?

부모가 있는 한 항상 존재해야 해서 따로 만들거나 지울 수 없기 때문입니다. 생성은 부모 만들 때 자동, 삭제는 부모 지울 때 자동입니다.

Q3. 왜 삭제 대신 reset인가?

싱글톤 서브리소스는 부모가 살아 있는 한 삭제 자체가 불가능합니다. 그래서 '초기화'가 필요하면 기본값으로 되돌리는 reset이 의미상 정확합니다.

Q4. 왜 싱글톤은 다른 싱글톤의 부모가 못 되나?

싱글톤이 부모 노릇을 하면 완전한 부모 자원처럼 무거운 책임을 다 떠안아야 합니다. 이는 '중간 형태'라는 단순함을 깨뜨립니다.


한 걸음 더 ▸ (지금 몰라도 됨)

  • 떼어낼 크기 기준은 정답이 없습니다. 실무에서는 "대부분의 호출에서 쓰이지 않는가"를 더 중요한 신호로 봅니다.
  • 표준 규칙은 "부모 밑에, 조회·수정만, 삭제는 reset"입니다. 예외 상황의 미세 조정은 실무에서 사례별로 다릅니다.
  • 더 깊이 보고 싶다면 Google AIP-156(싱글톤 자원 설계 지침)을 참고하세요: https://google.aip.dev/156 (검증일 2026-06-04, searxng — 문서 존재 및 주제 일치 확인)

마무리 한 장 요약

질문
언제 떼어내나? 크기·보안·휘발성이 다를 때
떼어낸 건 뭔가? 속성과 자원의 중간, 싱글톤 서브리소스
어떻게 동작하나? 조회·수정만 따로, 생성·삭제는 부모 따라 자동, 목록 없음
비우려면? 삭제가 아니라 reset
어디에 두나? 반드시 부모 밑, 최상위 금지, 자기는 부모 못 됨
대가는? 부모와 동시 수정 불가, 같은 종류 하나만

다음 장 예고: 다음 장에서는 자식이 부모를 바꿔 옮겨 다는 방법을 다룹니다. 지금은 몰라도 됩니다.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플